/**
 * 
 */
package org.gbif.agi.service.impl;

import java.util.List;

import org.gbif.agi.model.Taxon;
import org.gbif.agi.service.TaxonManager;
import org.gbif.agi.service.schedule.NestedSetLauncher;
import org.gbif.agi.service.schedule.TieTaxonomyLauncher;
import org.gbif.scheduler.model.Job;
import org.gbif.scheduler.service.JobManager;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author timrobertson
 */
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public class TaxonManagerImpl extends HibernateDaoSupport implements TaxonManager {
	private NestedSetLauncher nestedSetLauncher;
	private JobManager jobManager;
	
	@SuppressWarnings("unchecked")
	public List<Taxon> getChildrenOf(long id) {
		return getHibernateTemplate()
			.find("select t from Taxon t where t.parent.id = ? order by t.name", id);      
	}
	
	@SuppressWarnings("unchecked")
	public Taxon get(long id) {
		return (Taxon) getHibernateTemplate()
			.load(Taxon.class, id);      
	}
	
	@SuppressWarnings("unchecked")
	public List<Taxon> getRootTaxa(long agiDatasource) {
		return getHibernateTemplate()
			.find("select t from Taxon t where t.parent is null and t.agiDatasource.id=? order by t.name", agiDatasource);
	}

	public void buildNestedSet(long agiDatasourceId) {
		long time = System.currentTimeMillis();
		logger.info("Building nested set for datasource: " + agiDatasourceId);
		int count = 0;
		for (Taxon t : getRootTaxa(agiDatasourceId)) {
			count = nestedSetNode(t, count);			
		}
		getHibernateTemplate().flush();
		logger.info("Built nested set for datasource[" + agiDatasourceId + "] in " + ((System.currentTimeMillis()-time+1)/1000) + " secs");
	}
	
	public void scheduleNestedSet(long agiDatasourceId) {
		Job job = NestedSetLauncher.createJob(agiDatasourceId);
		jobManager.save(job);
	}
	
	/**
	 * Does the nested set setting for a node recursively
	 */
	protected int nestedSetNode(Taxon node, int count) {
		node.setLeft((long)count++);
		for (Taxon n : node.getChildren()) {
			count = nestedSetNode(n, count);
			if (count%1000 == 0) getHibernateTemplate().flush();
		}
		node.setRight((long)count++);
		getHibernateTemplate().save(node);
		
		return count;
	}
	
	public void scheduleTieGeneraToTaxonomy(long agiDatasourceId) {
		Job job = TieTaxonomyLauncher.createJob(agiDatasourceId);
		jobManager.save(job);
	}
	
	/**
	 * TODO This is not correct code yet... but something to quickly tie things together
	 * @param agiDatasourceId to tie to
	 */
	public void tieGeneraToTaxonomy(long agiDatasourceId) {
		logger.info("Clearing tied taxonomy for datasource: " + agiDatasourceId);
		getHibernateTemplate().getSessionFactory().getCurrentSession()
			.createSQLQuery("delete from genus_taxon where datasource_id=:id")
			.setLong("id", agiDatasourceId)
			.executeUpdate();
		
		logger.info("Tieing taxonomy for datasource: " + agiDatasourceId);
		long time = System.currentTimeMillis();
		getHibernateTemplate().getSessionFactory().getCurrentSession()
			.createSQLQuery("insert into genus_taxon(datasource_id,genus_id,taxon_lft) " + 
							"select :id, g.id, t.lft " + 
							"from genus g inner join taxon t on g.canonical=t.name")
			.setLong("id", agiDatasourceId)
			.executeUpdate();
		logger.info("Tied taxonomy for datasource[" + agiDatasourceId + "] in " + ((System.currentTimeMillis()-time+1)/1000) + " secs");
	}

	public NestedSetLauncher getNestedSetLauncher() {
		return nestedSetLauncher;
	}

	public void setNestedSetLauncher(NestedSetLauncher nestedSetLauncher) {
		this.nestedSetLauncher = nestedSetLauncher;
	}

	public JobManager getJobManager() {
		return jobManager;
	}

	public void setJobManager(JobManager jobManager) {
		this.jobManager = jobManager;
	}
}
